﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Collections.Generic;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.Web.UI.HtmlControls;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// Server control used to handle style and script referencing in addition to unified generation of load and ready scripts.
	/// </summary>
	public class Manager : Control {


		#region Constants: Property defaults

		private const string defaultTheme = "";
		private const MasterBoolean defaultUseCssStyling = MasterBoolean.ByControl;

		#endregion


		#region Static Properties: Helper

		/// <summary>
		/// Gets the GreenIsomer.Web.Controls.JQuery.Manager from the page's items dictionary.
		/// </summary>
		/// <param name="page">The System.Web.UI.Page the rendering control resides within.</param>
		/// <returns>The jQuery Manager control contained in the items dictionary.</returns>
		/// <exception cref="ArgumentNullException">A page reference is required.</exception>
		protected static Manager Get(Page page) {
			if(page == null) throw new ArgumentNullException("page");

			return page.Items[typeof(Manager)] as Manager;
		}

		/// <summary>
		/// Sets the GreenIsomer.Web.Controls.JQuery.Manager into the page's items dictionary.
		/// </summary>
		/// <param name="page">The System.Web.UI.Page the rendering control resides within.</param>
		/// <param name="value">The jQuery Manager control to place into the items dictionary.</param>
		protected static void Set(Page page, Manager value) {
			if(page == null) throw new ArgumentNullException("page");
			if(value == null) throw new ArgumentNullException("value");

			page.Items[typeof(Manager)] = value;
		}

		#endregion


		#region Static Methods: Public

		/// <summary>
		/// Returns the required single instance of the GreenIsomer.Web.Controls.JQuery.Manager control.
		/// </summary>
		/// <param name="page">The System.Web.UI.Page the rendering control resides within.</param>
		/// <returns>The required single instance of the jQuery Manager control.</returns>
		/// <exception cref="ArgumentNullException">A page reference is required.</exception>
		/// <exception cref="InvalidOperationException">Page must contain an instance of GreenIsomer.Web.Controls.JQuery.Manager.</exception>
		public static Manager GetCurrent(Page page) {
			Manager manager = Get(page);
			if(manager == null) throw new InvalidOperationException("Page must contain an instance of " + typeof(Manager).FullName + ".");
			return manager;
		}

		#endregion


		#region Static Methods: Private routines

		private static void ReferenceStyle(ControlCollection headerControls, string style, int index) {

			HtmlLink link = new HtmlLink();

			link.Attributes["rel"] = "Stylesheet";
			link.Attributes["type"] = "text/css";
			//link.Attributes["media"] = "screen,projection";
			link.Href = style;

			headerControls.AddAt(index, link);
		}

		#endregion


		#region Members

		private List<IJQueryControl> registeredControls;
		private List<string> registeredScriptFiles;
		private bool themeStyleFileRegistered;
		private List<string> registeredStyleFiles;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.Manager class.
		/// </summary>
		public Manager() {
			registeredControls = new List<IJQueryControl>();
			registeredScriptFiles = new List<string>();
			registeredStyleFiles = new List<string>();
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets or sets the theme in which to display all jQuery UI widgets.
		/// </summary>
		[DefaultValue(defaultTheme), Category("Appearance")]
		public virtual string Theme {
			get { return ViewState.GetValue("Theme", defaultTheme); }
			set { ViewState["Theme"] = value; }
		}

		/// <summary>
		/// Gets or sets a value instructing all jQuery server controls on the page (or control by control by default) not to render style defined as HTML attributes on the element, rather requiring the site's stylesheet to handle functionality required to properly render the client UI.
		/// </summary>
		public virtual MasterBoolean UseCssStyling {
			get { return ViewState.GetValue("UseCssStyling", defaultUseCssStyling); }
			set { ViewState["UseCssStyling"] = value; }
		}

		#endregion


		#region Methods: Registration

		/// <summary>
		/// Registers a control to receive RenderLoadScript and RenderReadyScript calls at the appropriate time.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="control">The jQuery server control to receive the appropriate jQuery render calls.</param>
		public virtual void RegisterControl<T>(T control) where T : Control, IJQueryControl {
			registeredControls.Add(control);
		}

		/// <summary>
		/// Registers a required script file to be linked from the rendered page's header.
		/// </summary>
		/// <param name="path">The server path in which to find the script file.</param>
		/// <exception cref="ArgumentNullException">Path cannot be null.</exception>
		/// <exception cref="ArgumentException">Path value cannot be an empty string.</exception>
		public virtual void RegisterScriptFile(string path) {
			RegisterFile(path, registeredScriptFiles);
		}

		/// <summary>
		/// Registers a required stylesheet file to be linked from the rendered page's header.
		/// </summary>
		/// <param name="path">The server path in which to find the stylesheet file.</param>
		/// <exception cref="ArgumentNullException">Path cannot be null.</exception>
		/// <exception cref="ArgumentException">Path value cannot be an empty string.</exception>
		public virtual void RegisterStyleFile(string path) {
			RegisterFile(path, registeredStyleFiles);
		}

		/// <summary>
		/// Registers the currently active theme stylesheet file to be linked from the rendered page's header.
		/// </summary>
		public virtual void RegisterThemeStyleFile() {
			themeStyleFileRegistered = true;
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Raises the System.Web.UI.Control.Init event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected override void OnInit(EventArgs e) {
			base.OnInit(e);

			if(DesignMode) return;

			if(Get(Page) != null) throw new InvalidOperationException("Page can contain only one instance of " + typeof(Manager).FullName + ".");
			Set(Page, this);
		}

		/// <summary>
		/// Raises the System.Web.UI.Control.PreRender event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected override void OnPreRender(EventArgs e) {
			ReferenceStyles();
			ReferenceScripts();

			base.OnPreRender(e);
		}

		/// <summary>
		/// Sends server control content to a provided System.Web.UI.HtmlTextWriter object, which writes the content to be rendered to the client.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A System.Web.UI.HtmlTextWriter that represents the output stream to render HTML content on the client.</param>
		protected override void Render(HtmlTextWriter writer) {
			if(writer == null) throw new ArgumentNullException("writer");

			if(registeredControls.Count == 0) return;

			ScriptManager manager = Page.Items[typeof(ScriptManager)] as ScriptManager;
			bool isInAsyncPostBack = manager != null && manager.IsInAsyncPostBack;

			Dictionary<Control, string> loadScripts = GetRegisteredLoadScripts();
			Dictionary<Control, string> readyScripts = GetRegisteredReadyScripts();

			if(loadScripts.Count == 0 && readyScripts.Count == 0) return;

			int counter = 0;
			if (isInAsyncPostBack) {
				foreach(KeyValuePair<Control, string> script in loadScripts)
					ScriptManager.RegisterStartupScript(script.Key, typeof(Manager), Convert.ToString(counter++, CultureInfo.InvariantCulture), script.Value, true);
				foreach(KeyValuePair<Control, string> script in readyScripts)
					ScriptManager.RegisterStartupScript(script.Key, typeof(Manager), Convert.ToString(counter++, CultureInfo.InvariantCulture), script.Value, true);
			}
			else
			{
				writer.Indent--;
				writer.WriteLine("<script type=\"text/javascript\">");
				writer.WriteLine("//<![CDATA[");

				if (loadScripts.Count != 0) {
					writer.WriteLine("$(function(){");
					foreach(string script in loadScripts.Values)
						writer.WriteLine(script);
					writer.WriteLine("});");
				}
				if (readyScripts.Count != 0) {
					writer.WriteLine("$(document).ready(function(){");
					foreach (string script in readyScripts.Values)
						writer.WriteLine(script);
					writer.WriteLine("});");
				}

				writer.WriteLine("//]]>");
				writer.WriteLine("</script>");
			}

		}

		/// <summary>
		/// Adds all referenced script files into the page's header.
		/// This method is used primarily by control developers.
		/// </summary>
		protected virtual void ReferenceScripts() {

			ControlCollection headerControls = Page.Header.Controls;

			foreach(string script in registeredScriptFiles) {
				HtmlGenericControl reference = new HtmlGenericControl("script");
				reference.Attributes["type"] = "text/javascript";
				reference.Attributes["src"] = script;

				headerControls.Add(reference);
			}
		}

		/// <summary>
		/// Adds all referenced stylesheet files into the page's header.
		/// This method is used primarily by control developers.
		/// </summary>
		protected virtual void ReferenceStyles() {

			ControlCollection headerControls = Page.Header.Controls;
			int index = 0;

			foreach(string style in registeredStyleFiles) {
				ReferenceStyle(headerControls, style, index++);
			}

			if(themeStyleFileRegistered) {
				string theme = Theme;
				if(string.IsNullOrEmpty(theme)) theme = Settings.Default.DefaultTheme;

				if(!string.IsNullOrEmpty(theme))
					ReferenceStyle(headerControls, string.Format(CultureInfo.InvariantCulture, Settings.Default.UiThemeStyleFileFormat, theme), index++);
			}
		}

		#endregion


		#region Methods: Private routines

		private Dictionary<Control, string> GetRegisteredLoadScripts() {
			Dictionary<Control, string> scripts = new Dictionary<Control, string>();

			foreach(IJQueryControl control in registeredControls) {
				if(!control.Visible) continue;

				ScriptBuilder script = new ScriptBuilder();
				script.Indent++;

				control.RenderLoadScript(script);
				if(script.Length != 0) scripts.Add((Control)control, script.ToString());
			}

			return scripts;
		}

		private Dictionary<Control, string> GetRegisteredReadyScripts() {
			Dictionary<Control, string> scripts = new Dictionary<Control, string>();

			foreach(IJQueryControl control in registeredControls) {
				if(!control.Visible) continue;

				ScriptBuilder script = new ScriptBuilder();
				script.Indent++;

				control.RenderReadyScript(script);
				if(script.Length != 0) scripts.Add((Control)control, script.ToString());
			}

			return scripts;
		}

		private void RegisterFile(string path, List<string> list) {
			if(path == null) throw new ArgumentNullException("path");
			if(path.Length == 0) throw new ArgumentException("A value is required", "path");

			string resolvedPath = ResolveUrl(path);
			if(list.Contains(resolvedPath)) return;

			list.Add(resolvedPath);
		}

		#endregion


	}

}